package com.esender.strategy.filter.impl;

import com.esender.common.constant.CacheConstant;
import com.esender.common.constant.SmsConstant;
import com.esender.common.enums.ExceptionEnums;
import com.esender.common.exception.StrategyException;
import com.esender.common.model.StandardSubmit;
import com.esender.strategy.client.BeaconCacheClient;
import com.esender.strategy.filter.StrategyFilter;
import com.esender.strategy.util.ErrorSendMsgUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.ZoneOffset;

/**
 * 一小时发送验证码短信限流规则
 * 一小时只能发送3次
 */
@Service("LimitOneHour")
@Slf4j
public class LimitOneHourStrategyFilter implements StrategyFilter {
    private final String UTC = "+8";
    private static final long ONE_HOUR = 1000 * 60 * 60 -1;

    private static final int LIMIT_HOUR = 3;
    private final int RETRY_COUNT = 2;
    @Autowired
    private BeaconCacheClient beaconCacheClient;
    @Autowired
    private ErrorSendMsgUtil errorSendMsgUtil;
    @Override
    public void strategy(StandardSubmit standardSubmit) {
        // 判断短信类型不是验证码类的，直接结束方法
        if(standardSubmit.getState() != SmsConstant.CODE_TYPE){
            return;
        }
        // 1、基于standardSubmit获取短信发送得时间
        LocalDateTime sendTime = standardSubmit.getSendTime();
        // 2、基于LocalDateTime获取短信发送得毫秒值
        Long sendTimeMillis = sendTime.toInstant(ZoneOffset.of(UTC)).toEpochMilli();
        standardSubmit.setOneHourLimitMilli(sendTimeMillis);
        // 3、获取客户得客户标识和手机号
        String mobile = standardSubmit.getMobile();
        Long clientId = standardSubmit.getClientId();
        // 4、优先将当前短信发送信息插入到zset结构中
        String key = CacheConstant.LIMIT_HOURS + clientId + CacheConstant.SEPARATOR + mobile;
        // 这里是可以重试得，当并发插入失败得时候，因为一小时内可以重复发送3次，那么这里得重试次数就最多只能有2次
        int retry = 0;
        while(!beaconCacheClient.zadd(key,standardSubmit.getOneHourLimitMilli(), standardSubmit.getOneHourLimitMilli())){
            // 退出出口
            if (retry == RETRY_COUNT) break;
            retry++;
            // 插入失败，是因为存储的member不允许重复，既然重复了，将时间向后移动，移动到当前系统时间
            standardSubmit.setOneHourLimitMilli(System.currentTimeMillis());
        }
        // 如果retry为2，代表已经重试了2次，但是依然没有成功说明已经有3次在zset中了
        if (retry == RETRY_COUNT){
            log.info("【策略模块-一小时限流策略】  插入失败！ 满足一小时限流规则，无法发送！");
            standardSubmit.setErrorMsg(ExceptionEnums.ONE_HOUR_LIMIT + ",mobile = " + mobile);
            errorSendMsgUtil.sendWriteLog(standardSubmit);
            errorSendMsgUtil.sendPushReport(standardSubmit);
            throw new StrategyException(ExceptionEnums.ONE_HOUR_LIMIT);
        }
        // 没有重试2次，3次之内，将数据正常的插入了。基于zrangebyscore做范围查询
        long start = standardSubmit.getOneHourLimitMilli() - ONE_HOUR;
        int count = beaconCacheClient.zRangeByScore(key, Double.parseDouble(start + ""), Double.parseDouble(standardSubmit.getOneHourLimitMilli() + ""));
        if(count > LIMIT_HOUR){
            log.info("【策略模块-一小时限流策略】  插入失败！ 满足一小时限流规则，无法发送！");
            beaconCacheClient.zRemove(key,standardSubmit.getOneHourLimitMilli() + "");
            standardSubmit.setErrorMsg(ExceptionEnums.ONE_HOUR_LIMIT + ",mobile = " + mobile);
            errorSendMsgUtil.sendWriteLog(standardSubmit);
            errorSendMsgUtil.sendPushReport(standardSubmit);
            throw new StrategyException(ExceptionEnums.ONE_HOUR_LIMIT);
        }
        log.info("【策略模块-一小时限流策略】  一小时限流规则通过，可以发送！");

    }
}
